Kealdish's Studio.

iOS APP中调用私有方法

字数统计: 952阅读时长: 3 min
2016/09/04 Share

前言

得益于苹果封闭的生态系统,iOS平台才能如此优秀和健壮。然而,对于开发者而言,开源永远是最受欢迎的,闭源只会让开发者束手束脚。我们在开发的过程中可能会遇到有需求需要依赖于iOS私有API,但由于iOS的封闭,我们无法直接调用私有方法,这让人很是头疼。那有没有方法能让我们在不越狱的情况下去调用私有API呢?

答案是有的,并且就我目前了解而言有两种方案。为了更好地解释这两种方案,我写了个小Demo。在Demo中只有一个类TestClass

1
2
3
4
5
6
7
// TestClass.h
#import <Foundation/Foundation.h>
@interface TestClass : NSObject
- (void)publicMethod;
@end
1
2
3
4
5
6
7
8
9
10
11
12
// TestClass.m
#import "TestClass.h"
@implementation TestClass
- (void)publicMethod{
NSLog(@"Call public method");
}
- (void)privateMethod{
NSLog(@"call private method");
}
@end

在这个类中有两个方法:publicMethodprivateMethod。其中,publicMethod是公有方法,privateMethod是私有方法。如果我们直接在实例中调用privateMethod,会得到”No visible @interface for ‘TestClass’ declares the selector ‘privateMethod’”的错误。因此,直接调用私有方法肯定是行不通的,那就换个套路试试。

1
2
3
4
5
6
7
8
9
10
11
// main.m
#import <Foundation/Foundation.h>
#import "TestClass.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestClass *obj = [[TestClass alloc] init];
[obj privateMethod];
}
return 0;
}

利用runtime特性

这里需要用到两个runtime相关的方法:respondsToSelector:performSelector:。前者是用来判断类或者类实例是否实现了某个方法选择子,后者是调用指定的方法选择子。

这里提一下performSelector:与直接调用方法之间的区别。由于OC的运行时,使得程序可以在运行时动态添加方法,因此在编译时动态方法是不存在的。为了能调用动态方法,就有了performSelector:。因此两者的区别就在于直接调用方法在编译期是会自动校验公有方法,若不存在该方法则编译无法通过,而调用performSelector:则在编译期不会进行校验,它会在运行时去查找并调用指定方法,若运行时找不到指定方法则会发生崩溃。考虑到程序的健壮性,会先调用respondsToSelector:检查方法是否存在。

采用该方案去调用privateMethod的正确做法应该是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
// main.m
#import <Foundation/Foundation.h>
#import "TestClass.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestClass *obj = [[TestClass alloc] init];
if ([obj respondsToSelector:@selector(privateMethod)]) {
[obj performSelector:@selector(privateMethod)];
}
}
return 0;
}

匿名category欺骗编译器

从刚才的方案中我们知道,直接调用方法会失败是因为编译器自动校验失败而报错,那如果我们欺骗编译器让它误以为校验的方法存在呢?这倒是个不错的思路,第二个方案也就由此产生。那就是写一个匿名的category声明想要调用的私有方法,这样编译器就会认为该方法存在而不会报错。

采用该方案调用privateMethod的正确姿势是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// main.m
#import <Foundation/Foundation.h>
#import "TestClass.h"
@interface TestClass()
- (void)privateMethod;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestClass *obj = [[TestClass alloc] init];
[obj privateMethod];
}
return 0;
}

结语

理论上这两种方法均可以实现对私有API的调用,不过,我更推荐第一种方案。因为第一种方案在程序的健壮性上会更好,并且书写上也更简单。友情提醒,可以在程序中调用私有API并不意味着就可以通过苹果的审核。其实,我也不太懂苹果的审核机制,有些APP调用私有API可以通过审核,有些APP则会被拒绝,所以,要想通过审核往往是要碰运气的。

CATALOG
  1. 1. 前言
  2. 2. 利用runtime特性
  3. 3. 匿名category欺骗编译器
  4. 4. 结语